#include "path_net.h"

namespace ieda_contest
{

void PathNet::doGateSizing()
{
    auto setup_violation_end_pts = findViolationNode();
    for (auto node : setup_violation_end_pts)
    {
        optimize(node);
    }
}

void PathNet::optimize(StaVertex *vertex)
{
    StaSeqPathData *worst_path_rise = _timing_engine->vertexWorstRequiredPath(
        vertex, AnalysisMode::kMax, TransType::kRise);
    StaSeqPathData *worst_path_fall = _timing_engine->vertexWorstRequiredPath(
        vertex, AnalysisMode::kMax, TransType::kFall);
    Slack worst_slack_rise = worst_path_rise->getSlackNs();
    Slack worst_slack_fall = worst_path_fall->getSlackNs();
    StaSeqPathData *worst_path =
        worst_slack_rise > worst_slack_fall ? worst_path_fall : worst_path_rise;

#ifndef MULTIPATH
    vector<TimingEngine::PathNet> timing_path =
        _timing_engine->getPathDriverVertexs(worst_path);
    int path_length = timing_path.size();

    for (int i = 0; i < path_length; i++)
    {
        auto path_net = timing_path[i];
        auto net = path_net.driver->get_design_obj()->get_net();

        auto drvr_pin = net->getDriver();
        if (!drvr_pin->isPin())
        {
            continue;
        }
        // auto drvr_inst = dynamic_cast<ista::Pin *>(drvr_pin)->get_own_instance();

        auto loads = net->getLoads();
        for (auto load : loads)
        {
            if (!load->isPin())
            {
                continue;
            }
            // 负载单元的时序信息
            auto load_pin = dynamic_cast<ista::Pin *>(load);
            auto *the_vertex = _timing_engine->findVertex(load_pin->getFullName().c_str());
            Slack load_pin_slack = getWorstSlack(the_vertex, AnalysisMode::kMax);
            if (load_pin_slack > 0.0)
            {
                // downsize
                auto load_cell = load_pin->get_cell_port()->get_ower_cell();
                Vector<LibertyCell *> *equiv_cells = _timing_engine->equivCells(load_cell);
                if (equiv_cells)
                {
                    // std::cout << "equiv_cells size -> " << equiv_cells->size() << std::endl;
                    const char *drvr_port_name = load_pin->get_cell_port()->get_port_name();

                    std::sort(equiv_cells->begin(), equiv_cells->end(),
                                [=](LibertyCell *cell1, LibertyCell *cell2)
                                {
                                    LibertyPort *port1 = cell1->get_cell_port_or_port_bus(drvr_port_name);
                                    LibertyPort *port2 = cell2->get_cell_port_or_port_bus(drvr_port_name);
                                    // return (port1->driveResistance() > port2->driveResistance());
                                    return (port1->get_port_cap() < port2->get_port_cap());
                                });
                    repowerInstance(load_pin->get_own_instance(), equiv_cells->front());
                }
            }
            // auto load_inst = load_pin->get_own_instance();
            // auto load_cell = load_pin->get_own_cell();
            // auto load_lib_cell = load_cell->getLibertyCell();
            // auto load_cell_area = load_lib_cell->getArea();
            // auto load_cell_pin = load_lib_cell->getPin("A");
            // auto load_cell_pin_cap = load_cell_pin->getCapacitance();

            // auto load_inst = dynamic_cast<ista::Pin *>(load)->get_own_instance();
        }
    }
#else
    vector<TimingEngine::PathNet> timing_path = _timing_engine->getPathDriverVertexs(worst_path);
    vector<ista::StaArc *> arcs = timing_path[0].driver->get_snk_arcs();

    ista::StaVertex *_clk_start_vertex = nullptr;
    for (auto arc : arcs)
    {
        if (arc->get_src()->is_clock())
        {
            _clk_start_vertex = arc->get_src();
            break;
        }
    }
    auto worst_paths = _timing_engine->getViolatedSeqPathsBetweenTwoSinks(
        timing_path[timing_path.size() - 1].load->getName().c_str(),
        _clk_start_vertex->getName().c_str(),
        AnalysisMode::kMax);
    // int max_path_read = 4;
    // int b = 0;
    // while (!worst_paths.empty() && b < max_path_read) {
    while (!worst_paths.empty())
    {
        // for (auto worst_path : worst_paths)
        // {
        vector<TimingEngine::PathNet> timing_path =
            _timing_engine->getPathDriverVertexs(worst_paths.top());
        worst_paths.pop();
        int path_length = timing_path.size();

        for (int i = 0; i < path_length; i++)
        {
            auto path_net = timing_path[i];
            auto net = path_net.driver->get_design_obj()->get_net();

            auto drvr_pin = net->getDriver();
            if (!drvr_pin->isPin())
            {
                continue;
            }
            // auto drvr_inst = dynamic_cast<ista::Pin *>(drvr_pin)->get_own_instance();

            auto loads = net->getLoads();
            for (auto load : loads)
            {
                if (!load->isPin())
                {
                    continue;
                }
                // 负载单元的时序信息
                auto load_pin = dynamic_cast<ista::Pin *>(load);
                auto *the_vertex = _timing_engine->findVertex(load_pin->getFullName().c_str());
                Slack load_pin_slack = getWorstSlack(the_vertex, AnalysisMode::kMax);
                if (load_pin_slack > 0)
                {
                    // downsize
                    auto load_cell = load_pin->get_cell_port()->get_ower_cell();
                    Vector<LibertyCell *> *equiv_cells = _timing_engine->equivCells(load_cell);
                    if (equiv_cells)
                    {
                        // std::cout << "equiv_cells size -> " << equiv_cells->size() << std::endl;
                        const char *drvr_port_name = load_pin->get_cell_port()->get_port_name();

                        std::sort(equiv_cells->begin(), equiv_cells->end(),
                                    [=](LibertyCell *cell1, LibertyCell *cell2)
                                    {
                                        LibertyPort *port1 = cell1->get_cell_port_or_port_bus(drvr_port_name);
                                        LibertyPort *port2 = cell2->get_cell_port_or_port_bus(drvr_port_name);
                                        // return (port1->driveResistance() > port2->driveResistance());
                                        return (port1->get_port_cap() < port2->get_port_cap());
                                    });
                        repowerInstance(load_pin->get_own_instance(), equiv_cells->front());
                    }
                }
            }
        }
        // }
    }
#endif
}

bool PathNet::repowerInstance(Instance *inst, LibertyCell *replace)
{
    const char *replacement_name = replace->get_cell_name();

    ista::TimingIDBAdapter *idb_adapter = dynamic_cast<TimingIDBAdapter *>(_timing_engine->get_db_adapter());
    idb::IdbLayout *layout = idb_adapter->get_idb()->get_def_service()->get_layout();
    idb::IdbCellMaster *replacement_master =
        layout->get_cell_master_list()->find_cell_master(replacement_name);
    if (replacement_master)
    {
        // idb::IdbInstance   *dinst = idb_adapter->staToDb(inst);
        // idb::IdbCellMaster *idb_master = dinst->get_cell_master();
        // Master             *master = new Master(idb_master);
        // float               area_master = DesignCalculator::calcMasterArea(master, _dbu);

        // increDesignArea(-area_master);

        // idb::IdbCellMaster *replace_master_idb = idb_adapter->staToDb(replace);

        // Master *replace_master = new Master(replace_master_idb);
        // float   area_replace_master = DesignCalculator::calcMasterArea(replace_master, _dbu);

        idb_adapter->replaceCell(inst, replace);
        _timing_engine->repowerInstance(inst->get_name(), replace->get_cell_name());
        // increDesignArea(area_replace_master);

        return true;
    }
    return false;
}

Slack PathNet::getWorstSlack(StaVertex *vertex, AnalysisMode mode)
{
    auto rise_slack = vertex->getSlackNs(mode, TransType::kRise);
    Slack rise = rise_slack ? *rise_slack : kInf;
    auto fall_slack = vertex->getSlackNs(mode, TransType::kFall);
    Slack fall = fall_slack ? *fall_slack : kInf;
    Slack slack = std::min(rise, fall);
    return slack;
}

VertexSeq PathNet::findViolationNode()
{
    VertexSet end_points;
    VertexSeq setup_violations;
    auto *ista = _timing_engine->get_ista();
    StaGraph *the_graph = &(ista->get_graph());
    StaVertex *vertex;
    FOREACH_END_VERTEX(the_graph, vertex) { end_points.insert(vertex); }
    for (auto *end : end_points)
    {
        Slack slack = getWorstSlack(end, AnalysisMode::kMax);
        if (slack < 0.0)
        {
            setup_violations.emplace_back(end);
        }
    }
    return setup_violations;
}

} // namespace ieda_contest